package cn.jbolt.ai.core.fileSplit;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.jbolt.ai.core.AIPrompts;
import cn.jbolt.ai.klb.DocDataType;
import cn.jbolt.ai.klb.FileState;
import cn.jbolt.ai.klb.FileType;
import cn.jbolt.ai.klb.entity.AiDoc;
import cn.jbolt.ai.klb.entity.AiFile;
import cn.jbolt.ai.klb.service.AiDocService;
import cn.jbolt.util.*;
import com.jboltai.capability.model.JBoltAI;
import com.jboltai.capability.text.JBoltText;
import com.jboltai.event.ai.aichat.AIChatEvent;
import com.jboltai.event.text.TextSplitEvent;

import com.jboltai.util.other.JBoltOCR;
import com.jboltai.util.text.FileInfo;
import com.jboltai.util.text.TextUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;

import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;

/**
 *
 */
@Service
public class FileSplitServiceImpl implements FileSplitService {

    private static final Logger logger = LoggerFactory.getLogger(FileSplitServiceImpl.class);
    @Autowired
    private QiniuUtil qiniuUtil;
    @Autowired
    private UploadLocalUtil uploadLocalUtil;
    @Autowired
    private FilePathGeneratorUtil filePathGeneratorUtil;

    @Autowired
    private AiDocService docService;


    @Override
    public Result<String> split(AiFile file) {
        if (file.getDocType().equals(FileType.NORMAL.getCode())) {
            return splitNormalFile(file);
        }
        return splitQAFile(file);
    }

    /**
     * 构建一个包含 Excel 文件后缀的列表
     */
    public static List<String> getExcelFileExtensions() {
        List<String> excelExtensions = new ArrayList<>();
        excelExtensions.add("xlsx"); // Excel 2007 及以后版本
        excelExtensions.add("xls");  // Excel 2003 及以前版本
        excelExtensions.add("xlsm"); // 启用宏的工作簿
        excelExtensions.add("xlsb"); // Excel 二进制工作簿
        excelExtensions.add("csv");  // 逗号分隔值文件
        return excelExtensions;
    }

    /**
     * 检查Excel文件是否有数据
     *
     * @param reader
     * @return
     */
    private Boolean checkExcel(ExcelReader reader) {

        if (reader.getRowCount() == 0) {
            logger.error("Excel文件没有数据");
            return false;
        }
        int columnCount = reader.getColumnCount();
        if (columnCount < 2) {
            logger.error("Excel 文件列数不足，无法读取 问题 和 答案 数据");
            return false;
        }
        return true;
    }


    /**
     * 将Excel列转换为AiDoc列表
     * 此方法从指定的ExcelReader中读取两列数据（通常代表问题和答案），并将它们转换为AiDoc对象列表
     * 它跳过空值，确保只有当问题和答案都存在时，才创建AiDoc对象
     *
     * @param reader ExcelReader对象，用于读取Excel文件中的数据
     * @param file   AiFile对象，包含与Excel文件相关的信息，如知识库ID和文件ID
     * @return 返回一个AiDoc对象列表，每个对象代表一个问题-答案对
     */
    private List<AiDoc> convertExcelColumnsToAiDocList(ExcelReader reader, AiFile file) {
        // 初始化AiDoc列表
        List<AiDoc> docs = new ArrayList<>();
        // 读取第一列（问题）的数据，从第二行到最后一行
        List<Object> QList = reader.readColumn(0, 1, reader.getRowCount() - 1);
        // 读取第二列（答案）的数据，从第二行到最后一行
        List<Object> AList = reader.readColumn(1, 1, reader.getRowCount() - 1);

        // 遍历问题和答案列表，创建AiDoc对象
        for (int i = 0; i < Math.min(QList.size(), AList.size()); i++) {
            // 获取当前索引的问题和答案
            Object q = QList.get(i);
            Object a = AList.get(i);
            // 确保问题和答案都不为空
            if (q != null && a != null) {
                // 创建新的AiDoc对象，并设置其属性
                AiDoc aiDoc = new AiDoc();
                aiDoc.setContent(q.toString());
                aiDoc.setAnswer(a.toString());
                aiDoc.setKnowledgeBaseId(file.getKnowledgeBaseId());
                aiDoc.setFileId(file.getId());
                aiDoc.setDocType(FileType.QA.getCode());
                aiDoc.setDataType(DocDataType.FILE_PIECE.getCode());
                aiDoc.setDocStatus(FileState.TRAIN_SUCCESS.getCode());
                aiDoc.setTrainTime(DateTime.now());
                // 将AiDoc对象添加到列表中
                docs.add(aiDoc);
            }
        }
        // 返回AiDoc列表
        return docs;
    }


    /**
     * 读取指定路径Excel文件的前两列数据
     *
     * @param
     * @return
     */
    public List<AiDoc> readFirstTwoColumns(AiFile file) {
        List<AiDoc> docs = null;
        String networkUrl = file.getFilePath();
        try (InputStream inputStream = new URL(networkUrl).openStream(); ExcelReader reader = ExcelUtil.getReader(inputStream)) {
            //检查excel的数据
            if (!checkExcel(reader)) {
                return docs;
            }
            //doc集合
            docs = convertExcelColumnsToAiDocList(reader, file);

        } catch (MalformedURLException e) {
            logger.error("网络地址格式错误: {}", networkUrl, e);
            return docs;
        } catch (IOException e) {
            logger.error("读取网络文件或 Excel 文件时发生 I/O 错误", e);
            return docs;
        }
        return docs;
    }


    /**
     * 拆分QA的excel
     *
     * @param file
     * @return
     */
    private Result splitQAFromExcel(AiFile file) {
        List<AiDoc> list = readFirstTwoColumns(file);
        if (list == null) {
            return Result.error("文件读取失败");
        }
        list.forEach(doc -> {
            FileSplitCache.addTotalCount(file.getId());
            docService.add(doc);
        });
        FileSplitCache.setSplitOver(file.getId());
        return Result.success();
    }

    /**
     * @param aiFile
     * @return
     */
    private Result splitQAFile(AiFile aiFile) {

        //如果是excel
        if (getExcelFileExtensions().contains(aiFile.getFileType().toLowerCase())) {
            FileSplitCache.addFileSplitCounter(aiFile.getId());
            return splitQAFromExcel(aiFile);
        }
        aiFile.setChunkSize(1000);
        aiFile.setChunkOverlap(0);
        Result<FileSplitResult> result = splitByChunk(aiFile);
        if (result.isError()) {
            return Result.error(result.getMsg());
        }
        try {
            FileSplitCache.addFileSplitCounter(aiFile.getId());
            StringBuilder aiProcessingDoc = new StringBuilder();
            //开始解析
            result = processBlocks(result.getData(), (block, count, index) -> {
                //拿到这个块，要继续让AI提取QA的
                AIChatEvent event = aiSplitText(aiProcessingDoc, block, aiFile);
                List<String> blocks = event.getAttr("blocks");
                extractQAFromBlocks(aiFile, blocks);

            });
            if (result.isError()) {
                return result;
            }
            if (aiProcessingDoc.length() > 0) {
                //还有剩余的文本
                extractQAFromBlock(aiFile, aiProcessingDoc.toString());
            }
            FileSplitCache.setSplitOver(aiFile.getId());
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("QA文件：{}, 处理失败", aiFile.getFilePath());
            return Result.error(e.getMessage());
        }
    }


    /**
     * AI
     *
     * @param doc
     */
    private AIChatEvent aiSplitText(StringBuilder processing, String doc, AiFile aiFile) {
        /*
          doc是 通过jbolttext根据token数分割的文本段，这个文本段接下来要交给ai 根据语义进行分割。
          分割的最后一部分，可能不是完整的，就留在processing中，和下一个 token文本段合并再交给ai处理。
         */
        processing.replace(0, processing.length(), mergeStrings(processing.toString(), doc));
        AIChatEvent event = JBoltAI.chat().prompt(AIPrompts.SPLIT_TEMPLATE, processing);
        if (aiFile.getAiModel() != null) {
            event.setModel(aiFile.getAiModel());
        }
        if (StrUtil.isNotBlank(aiFile.getDefaultModel())) {
            event.setModelName(aiFile.getDefaultModel()); //设置默认模型
        }
        event.setAttr("oriText", processing);
        event.setStream(false).setTemperature(0.1)
                .setMaxTokens(2000)
                .setHandler(new AISplitHandler(aiFile))
                .publish().await();

        if (event.isFail()) {
            throw new RuntimeException(event.getError().getMsg(), event.getError().getException());
        }

        return event;
    }

    /**
     * ai根据语义将文本分成了多个文本块，接下来将文本块交给大模型提取题目，但文本块可能太小，要凑500个token大小再去提取
     *
     * @param file
     * @param splitBlocks
     */
    private void extractQAFromBlocks(AiFile file, List<String> splitBlocks) {
        double tokenSum = 0;
        double itemToken = 0;
        StringBuilder tempStr = new StringBuilder();

        for (int i = 0; i < splitBlocks.size(); i++) {
            itemToken = TextUtil.calToken(splitBlocks.get(i));
            if (tokenSum + itemToken > 1000 && tokenSum > 0) {
                //满2000了，提取一次
                extractQAFromBlock(file, tempStr.toString());
                tempStr.setLength(0);
                tokenSum = 0;
            }
            tokenSum += itemToken;
            tempStr.append(splitBlocks.get(i));

        }
        if (tokenSum > 0) {
            extractQAFromBlock(file, tempStr.toString());
        }
    }

    private void extractQAFromBlock(AiFile file, String block) {
        AIChatEvent event = JBoltAI.chat().prompt(AIPrompts.EXTRACT_QA_TEMPLATE, block)
                .setStream(false).setHandler(new AIExtractQAHandler(file))
                .setMaxTokens(2000);
        if (file.getAiModel() != null) {
            event.setModel(file.getAiModel());
        }
        if (StrUtil.isNotBlank(file.getDefaultModel())) {
            event.setModelName(file.getDefaultModel()); //设置默认模型
        }
        event.publish();
        event.await();
        if (event.isFail()) {
            throw new RuntimeException(event.getError().getMsg(), event.getError().getException());
        }

        List<AiDoc> docs = event.getAttr("docs");
        docs.forEach(doc -> {
            FileSplitCache.addTotalCount(file.getId());
            docService.add(doc);
        });

    }


    /**
     * @param a
     * @param b
     * @return
     */
    public static String mergeStrings(String a, String b) {
        if (StrUtil.isBlank(a)) {
            return b;
        }
        if (StrUtil.isBlank(b)) {
            return a;
        }
        int maxOverlap = findMaxOverlap(a, b);
        return a + b.substring(maxOverlap);
    }

    private static int findMaxOverlap(String a, String b) {
        int low = 0;
        int high = Math.min(a.length(), b.length());
        int maxOverlap = 0;

        while (low <= high) {
            int mid = (low + high) / 2;
            if (a.endsWith(b.substring(0, mid))) {
                maxOverlap = mid;
                low = mid + 1;
            } else {
                high = mid - 1;
            }
        }
        return maxOverlap;
    }


    private Result splitNormalFile(AiFile aiFile) {
        if (aiFile.getChunkSize() == null || aiFile.getChunkSize() == 0) {
            return Result.error("请输入分块数量");
        }
        if (aiFile.getChunkOverlap() == null || aiFile.getChunkOverlap() == 0) {
            return Result.error("请输入重叠数量");
        }
        Result<FileSplitResult> result = splitByChunk(aiFile);
        if (result.isError()) {
            return Result.error(result.getMsg());
        }

        FileSplitCache.addFileSplitCounter(aiFile.getId());
        //开始解析
        result = processBlocks(result.getData(), (block, count, index) -> {
            FileSplitCache.addTotalCount(aiFile.getId());
            AiDoc doc = new AiDoc();
            doc.setContent(processBlockFilePrefixInfo(aiFile.getFileName(), block, index, count));
            doc.setKnowledgeBaseId(aiFile.getKnowledgeBaseId());
            doc.setFileId(aiFile.getId());
            doc.setDataType(DocDataType.FILE_PIECE.getCode());
            doc.setDocType(aiFile.getDocType());
            docService.add(doc);
        });
        FileSplitCache.setSplitOver(aiFile.getId());

        return result;

    }

    private String processBlockFilePrefixInfo(String fileName, String text, int index, int totalCount) {
        if (fileName.contains(".")) {
            fileName = fileName.substring(0, fileName.lastIndexOf("."));
        }
        //index 从0开始 需要自己+1
        return "# " + fileName + "(" + (index + 1) + "/" + totalCount + "):\n" + text;
    }

    /**
     * @param fileSplitResult
     * @param blockHandler
     */
    private Result processBlocks(FileSplitResult fileSplitResult, BlockHandler blockHandler) {
        Result result = Result.success();
        StringBuilder cache = new StringBuilder();
        try (RandomAccessFile accessFile = new RandomAccessFile(fileSplitResult.getTempFile(), "r")) {
            AtomicInteger counter = new AtomicInteger(0);
            FileUtil.readLines(accessFile, Charset.forName("utf-8"), (line) -> {
                if (result.isError()) return;
                try {
                    if (FilesUtil.splitSymbol.equals(line)) {
                        //上一个块结束了
                        blockHandler.handle(cache.toString(), fileSplitResult.getSplitCount(), counter.getAndIncrement());
                        cache.delete(0, cache.length());
                    } else {
                        cache.append(line);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    logger.error("文件分片处理失败。\n 分片：{}", cache.toString());
                    result.setState(Result.ERROR_STATE);
                }

            });
            if (cache.length() > 0) {
                blockHandler.handle(cache.toString(), fileSplitResult.getSplitCount(), counter.getAndIncrement());
                cache.delete(0, cache.length());
            }
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("文件处理失败，失败原因：" + e.getMessage());
            return Result.error("文件处理失败");
        } finally {
            FileUtil.del(fileSplitResult.getTempFile());
        }

    }


    /**
     * @param file
     */
    public Result<FileSplitResult> splitByChunk(AiFile file) {
        if (StrUtil.isBlankIfStr(file.getFilePath())) {
            return Result.error("请输入要提取的文档地址");
        }
        //构建一个临时文件存储分割的内容
        Result<File> result = FilesUtil.createFileInFolder(RandomUtil.randomString(8) + ".txt");
        if (result.isError()) {
            return Result.error(result.getMsg());
        }
        FileSplitResult fileSplitResult = new FileSplitResult(result.getData());
        try {
            TextSplitEvent event = configureTextSplitEvent(file.getFilePath(), file.getChunkSize(), file.getChunkOverlap());
            event.onSuccess((e, res) -> {
                String content = res.getChuncks().get(0) + "\n" + FilesUtil.splitSymbol + "\n";
                fileSplitResult.addSplitCount(res.getChuncks().size());
                logger.info("文档分割中，分割到内容:{}", content);
                FileUtil.appendUtf8String(content, fileSplitResult.getTempFile());
                logger.info("文档分割中内容已写入临时文件： {}", fileSplitResult.getTempFile().getAbsolutePath());
            });
            event.onFail((e, error) -> {
                logger.error("文档分割失败，文档地址为:{},失败原因:{}", file.getFilePath(), error.getFullMsg());
            });

            if (Objects.equals(file.getExtractResources(), true) || Objects.equals(file.getOcrImg(), true)) {
                event.onFile((e, fileInfo) -> {
                    String res = "";
                    if (Objects.equals(file.getOcrImg(), true) && fileInfo.isImage()) {
                        try {
                            res += JBoltOCR.read(fileInfo.getInputStream()) + "\n";
                        } catch (Exception ocrException) {
                            ocrException.printStackTrace();
                            logger.error("ocr识别文件[{}]图片时发生异常", file.getFileName());
                        }
                    }
                    if (Objects.equals(file.getExtractResources(), true)) {

                        if (file.getResourcesPosition().equals(AiFile.RESOURCES_QINIU)) {
                            res += uploadQiniu(fileInfo, file.getResourceSavePath());
                        } else {
                            res += uploadLocal(fileInfo, file.getResourceSavePath());
                        }
                    }
                    return res;
                });
            }

            event.publish().await();

            if (event.isFail()) {
                logger.error("文档分割失败，文档地址为:{},失败原因:{}", event.getUrl(), event.getError().getMsg());
                fileSplitResult.getTempFile().deleteOnExit();
                return Result.error(event.getError().getMsg());
            }


            return Result.success(fileSplitResult);
        } catch (Exception e) {
            logger.error("文档分割失败，文档地址为:{},失败原因:{}", file.getFilePath(), e.getMessage());
            return Result.error(e.getMessage());
        }
    }

    /**
     * onfile中上传资源资源到本地
     *
     * @param fileInfo
     * @param filePath
     * @return
     */
    private String uploadLocal(FileInfo fileInfo, String filePath) {
        try {
            String fileName = fileInfo.getFileName();
            if (StrUtil.isNotBlank(filePath)) {
                fileName = filePathGeneratorUtil.generateFilePath(filePath, fileName);
            }
            //构建一个filePath的名字解析
            fileInfo.getInputStream();
            Result<String> fileRes = uploadLocalUtil.uploadInputStreamToLocal(fileInfo.getInputStream(), fileName);
            if (fileRes.isSuccess()) {
                String path = fileRes.getData();
                logger.info("文档提取的文件上传成功, 文件地址: {}", path);
                return "![" + fileName + "](" + path + ")";
            } else {
                return "![" + fileName + "]('')";
            }
        } catch (Exception error) {
            String fileName = fileInfo.getFileName();
            logger.error("文档提取的文件上传失败,文件名: {}, 失败原因: {}", fileName, error.getMessage());
            return "![" + fileName + "]('')";
        }
    }

    /**
     * onfile中上传资源到七牛
     *
     * @param fileInfo
     * @param filePath
     * @return
     */
    private String uploadQiniu(FileInfo fileInfo, String filePath) {
        try {
            String fileName = fileInfo.getFileName();
            if (StrUtil.isNotBlank(filePath)) {
                filePath = filePathGeneratorUtil.generateFilePath(filePath, fileName);
            }
            Result<String> fileRes = qiniuUtil.uploadFile(fileInfo.getInputStream(), fileName, filePath);
            if (fileRes.isSuccess()) {
                String path = fileRes.getData();
                logger.info("文档提取的文件上传成功, 文件地址: {}", path);
                return "![" + fileName + "](" + path + ")";
            } else {
                return "![" + fileName + "]('')";
            }
        } catch (Exception error) {
            String fileName = fileInfo.getFileName();
            logger.error("文档提取的文件上传失败,文件名: {}, 失败原因: {}", fileName, error.getMessage());
            return "![" + fileName + "]('')";
        }
    }


    /**
     *
     */
    private TextSplitEvent configureTextSplitEvent(String url, Integer chunkSize, Integer chunkOverlap) {
        TextSplitEvent textSplitEvent = JBoltText.splitUrl(url).setStream(true);
        if (chunkSize != null && chunkSize > 0) {
            textSplitEvent.setChunkSize(chunkSize);
        }
        if (chunkOverlap != null && chunkOverlap >= 0) {
            textSplitEvent.setChunkOverlap(chunkOverlap);
        }


        return textSplitEvent;
    }


}